6.4.1 原子函数
原子函数能够以很底层的加锁机制来同步访问整型变量和指针。我们可以用原子函数来修正代码清单6-9中创建的竞争状态,如代码清单6-13所示。
代码清单6-13 listing13.go
01 // 这个示例程序展示如何使用atomic包来提供
02 // 对数值类型的安全访问
03 package main
04
05 import (
06 "fmt"
07 "runtime"
08 "sync"
09 "sync/atomic"
10 )
11
12 var (
13 // counter是所有goroutine都要增加其值的变量
14 counter int64
15
16 // wg用来等待程序结束
17 wg sync.WaitGroup
18 )
19
20 // main是所有Go程序的入口
21 func main() {
22 // 计数加2,表示要等待两个goroutine
23 wg.Add(2)
24
25 // 创建两个goroutine
26 go incCounter(1)
27 go incCounter(2)
28
29 // 等待goroutine结束
30 wg.Wait()
31
32 // 显示最终的值
33 fmt.Println("Final Counter:", counter)
34 }
35
36 // incCounter增加包里counter变量的值
37 func incCounter(id int) {
38 // 在函数退出时调用Done来通知main函数工作已经完成
39 defer wg.Done()
40
41 for count := 0; count < 2; count++ {
42 // 安全地对counter加1
43 atomic.AddInt64(&counter, 1)
44
45 // 当前goroutine从线程退出,并放回到队列
46 runtime.Gosched()
47 }
48 }
对应的输出如代码清单6-14所示。
代码清单6-14 listing13.go的输出
Final Counter: 4
现在,程序的第43行使用了 atmoic
包的 AddInt64
函数。这个函数会同步整型值的加法,方法是强制同一时刻只能有一个goroutine运行并完成这个加法操作。当goroutine试图去调用任何原子函数时,这些goroutine都会自动根据所引用的变量做同步处理。现在我们得到了正确的值4。
另外两个有用的原子函数是 LoadInt64
和 StoreInt64
。这两个函数提供了一种安全地读和写一个整型值的方式。代码清单6-15中的示例程序使用 LoadInt64
和 StoreInt64
来创建一个同步标志,这个标志可以向程序里多个goroutine通知某个特殊状态。
代码清单6-15 listing15.go
01 // 这个示例程序展示如何使用atomic包里的
02 // Store和Load类函数来提供对数值类型
03 // 的安全访问
04 package main
05
06 import (
07 "fmt"
08 "sync"
09 "sync/atomic"
10 "time"
11 )
12
13 var (
14 // shutdown是通知正在执行的goroutine停止工作的标志
15 shutdown int64
16
17 // wg用来等待程序结束
18 wg sync.WaitGroup
19 )
20
21 // main是所有Go程序的入口
22 func main() {
23 // 计数加2,表示要等待两个goroutine
24 wg.Add(2)
25
26 // 创建两个goroutine
27 go doWork("A")
28 go doWork("B")
29
30 // 给定goroutine执行的时间
31 time.Sleep(1 * time.Second)
32
33 // 该停止工作了,安全地设置shutdown标志
34 fmt.Println("Shutdown Now")
35 atomic.StoreInt64(&shutdown, 1)
36
37 // 等待goroutine结束
38 wg.Wait()
39 }
40
41 // doWork用来模拟执行工作的goroutine,
42 // 检测之前的shutdown标志来决定是否提前终止
43 func doWork(name string) {
44 // 在函数退出时调用Done来通知main函数工作已经完成
45 defer wg.Done()
46
47 for {
48 fmt.Printf("Doing %s Work\n", name)
49 time.Sleep(250 * time.Millisecond)
50
51 // 要停止工作了吗?
52 if atomic.LoadInt64(&shutdown) == 1 {
53 fmt.Printf("Shutting %s Down\n", name)
54 break
55 }
56 }
57 }
在这个例子中,启动了两个goroutine,并完成一些工作。在各自循环的每次迭代之后,在第52行中goroutine会使用 LoadInt64
来检查 shutdown
变量的值。这个函数会安全地返回 shutdown
变量的一个副本。如果这个副本的值为1,goroutine就会跳出循环并终止。
在第35行中, main
函数使用 StoreInt64
函数来安全地修改 shutdown
变量的值。如果哪个 doWork
goroutine试图在 main
函数调用 StoreInt64
的同时调用 LoadInt64
函数,那么原子函数会将这些调用互相同步,保证这些操作都是安全的,不会进入竞争状态。